fix(security): sanitize HTTP 500 responses to prevent info disclosure - #814
fix(security): sanitize HTTP 500 responses to prevent info disclosure#814groupthinking wants to merge 11 commits into
Conversation
…sure (supersedes #801) Multiple FastAPI handlers returned internal exception text to clients via `HTTPException(status_code=500, detail=str(e))` (or f-strings embedding `{e}`), leaking stack-adjacent messages, backend API errors, and database/Looker errors. This is an information-disclosure vector (CWE-209). #801 sanitized ~24 handlers but left three live endpoints leaking: `generate_video_pack` and `generate_blueprint` (v1/router.py) and the mounted `reporting_routes.py` dashboard endpoint — the exact gaps Copilot flagged on that PR. A tree-wide scan surfaced 13 further leaks in cloud_ai_routes.py, cloud_api_endpoints.py, and real_api_endpoints.py that #801 never touched. Changes: - Replace the dynamic `detail` in every 500 response across the backend with a static "Internal server error"; the full exception is now logged server-side (`logger.error(..., exc_info=True)`) so diagnostics are preserved. - Add reporting_routes.py a module logger (previously none). - Add tests/unit/test_500_info_disclosure.py: a hermetic source-scan guard that fails if any backend 500 response uses a dynamic `detail`, closing the test-coverage gap (existing exception-path tests asserted only status code, so they passed while the body leaked). Includes a self-check that the scanner detects a synthetic leak and ignores 4xx responses. 4xx responses (which echo client-supplied validation input) are intentionally left unchanged. Verified: all touched files compile; ruff findings are identical to the base branch (lint-neutral); guard test passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018MF2xHBKyQBQtpdXt6bVRx
…s in cloud/real routers Follow-up on the merge resolution (4c9b205): three 500 handlers still returned internal exception text, flagged in the Copilot review but not yet fixed: - cloud_api_endpoints.py: process_video_cloud returned a dict detail whose "message" embedded f"Cloud processing failed: {str(e)}"; process_video_task_handler returned detail=error_msg (same interpolation). - real_api_endpoints.py: process_video_real_api returned a dict detail embedding f"Real API processing failed: {str(e)}". All three now return a static detail="Internal server error". error_msg is still built and logged server-side via logger.error, so diagnostics are preserved; only the client-facing body is sanitized. No test asserted the leaked dict/message. Remaining, still-open items (already noted by the Copilot review, deferred as separate semantics-touching changes): the JSONResponse global_exception_handler in backend/main.py, the 503/429 handlers in cloud_ai_routes.py (exception ordering), positional HTTPException(500, str(e)) sites in api/advanced_video_routes.py, and upgrading the regression guard to AST so it covers those forms. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018MF2xHBKyQBQtpdXt6bVRx
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited) Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughUpdated FastAPI exception handlers across cloud AI, cloud API, and real API routes to return generic 500 responses while retaining server-side exception logging. ChangesAPI Error Handling
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
|
@coderabbitai review Generated by Claude Code |
|
✅ Action performedReview finished.
|
|
Closing as a duplicate of #807. This PR's head ( #807 is the canonical PR for this work: it's non-draft, older, carries the full investigation writeup (supersedes #801), and already has review history. Consolidating there so there aren't two competing PRs targeting protected Generated by Claude Code |
…itization) The existing test_error_response_includes_video_url asserted that the /api/v2/process-video 500 body echoed the request video_url — the exact CWE-209 information-disclosure behaviour the sanitization removes. It failed in CI (assert 'auJzb1D-fag' in 'Internal server error') because the handler now returns a static detail. Invert it into test_error_response_does_not_leak_internal_state: assert the detail is exactly 'Internal server error' and contains neither the video_url nor the exception text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JNphZoHcSj1jnc3ofnw5oc
|
Correction — not closing this after all; it now supersedes #807. After my earlier "duplicate of #807" note, CI on the shared commit That pre-existing test asserted the 500 body echoes the request So this branch = #807's diff + the test-regression fix #807 is missing. Recommend landing #814 and closing #807 as the stale one. Unrelated: the Still targeting protected Generated by Claude Code |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/youtube_extension/backend/cloud_api_endpoints.py (1)
221-226: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDo not persist exception text as client-visible task state.
Although the immediate 500 response is generic,
error_msgstill containsstr(e)and is written to Firestore at Line 220.get_video_statusandget_video_resultreturnstate.error_message, allowing clients to recover the exception through polling. Store a fixed user-safe message and log the original exception withexc_info=True; the new “logged above only” comment is currently inaccurate.As per coding guidelines, outputs must be sanitized for security.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/youtube_extension/backend/cloud_api_endpoints.py` around lines 221 - 226, Update the error-state persistence in the surrounding endpoint handler to store a fixed user-safe message instead of exception-derived error_msg text. Keep the original exception available only in the logger call, adding exc_info=True for traceback details, and revise the nearby comment to accurately describe this behavior; ensure get_video_status and get_video_result cannot expose str(e) through state.error_message.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/youtube_extension/backend/cloud_ai_routes.py`:
- Around line 230-232: Update the exception handlers in
src/youtube_extension/backend/cloud_ai_routes.py at lines 230-232, 264-271,
301-304, and 326-328, src/youtube_extension/backend/cloud_api_endpoints.py at
lines 144-149, and src/youtube_extension/backend/real_api_endpoints.py at lines
120-125 to preserve original tracebacks while raising sanitized HTTPException
responses. Change each logger.error call to include exc_info=True or use
logger.exception(...), while retaining the existing contextual messages and
response behavior.
In `@src/youtube_extension/backend/real_api_endpoints.py`:
- Around line 173-177: Preserve explicit HTTPException responses by adding an
HTTPException-specific re-raise before the broad Exception handler in both
batch_process_videos at src/youtube_extension/backend/real_api_endpoints.py
lines 173-177 and the search-result validation flow at lines 431-435; leave
other exceptions handled by the existing logging and 500 response path.
---
Outside diff comments:
In `@src/youtube_extension/backend/cloud_api_endpoints.py`:
- Around line 221-226: Update the error-state persistence in the surrounding
endpoint handler to store a fixed user-safe message instead of exception-derived
error_msg text. Keep the original exception available only in the logger call,
adding exc_info=True for traceback details, and revise the nearby comment to
accurately describe this behavior; ensure get_video_status and get_video_result
cannot expose str(e) through state.error_message.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: d1ab9536-b6da-4be3-a9b8-69d7ebc01c45
⛔ Files ignored due to path filters (2)
tests/unit/test_500_info_disclosure.pyis excluded by!tests/**tests/unit/test_cloud_routes.pyis excluded by!tests/**
📒 Files selected for processing (3)
src/youtube_extension/backend/cloud_ai_routes.pysrc/youtube_extension/backend/cloud_api_endpoints.pysrc/youtube_extension/backend/real_api_endpoints.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
groupthinking/uvai-skills(manual)
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: test
⚠️ CI failures not shown inline (10)
GitHub Actions: Security Scan / trivy: fix(security): sanitize HTTP 500 responses to prevent info disclosure
Conclusion: failure
`#9` 29.57 Setting up libgbm1:amd64 (25.0.7-2+deb13u1) ...
`#9` 29.57 Setting up libgl1-mesa-dri:amd64 (25.0.7-2+deb13u1) ...
`#9` 29.58 Setting up gcc-14 (14.2.0-19) ...
`#9` 29.58 Setting up librsvg2-2:amd64 (2.60.0+dfsg-1) ...
`#9` 29.59 Setting up libpocketsphinx3:amd64 (0.8+5prealpha+1-15+b4) ...
`#9` 29.59 Setting up libavcodec61:amd64 (7:7.1.5-0+deb13u1) ...
`#9` 29.59 Setting up g++-14-x86-64-linux-gnu (14.2.0-19) ...
`#9` 29.59 Setting up g++-x86-64-linux-gnu (4:14.2.0-1) ...
`#9` 29.60 Setting up curl (8.14.1-2+deb13u4) ...
`#9` 29.60 Setting up g++-14 (14.2.0-19) ...
`#9` 29.60 Setting up libsdl2-2.0-0:amd64 (2.32.4+dfsg-1) ...
`#9` 29.60 Setting up libglx-mesa0:amd64 (25.0.7-2+deb13u1) ...
`#9` 29.61 Setting up libglx0:amd64 (1.7.0-1+b2) ...
`#9` 29.61 Setting up libavformat61:amd64 (7:7.1.5-0+deb13u1) ...
`#9` 29.61 Setting up gcc (4:14.2.0-1) ...
`#9` 29.62 Setting up libgl1:amd64 (1.7.0-1+b2) ...
`#9` 29.63 Setting up libavfilter10:amd64 (7:7.1.5-0+deb13u1) ...
`#9` 29.63 Setting up g++ (4:14.2.0-1) ...
`#9` 29.64 update-alternatives: using /usr/bin/g++ to provide /usr/bin/c++ (c++) in auto mode
`#9` 29.64 Setting up build-essential (12.12) ...
`#9` 29.64 Setting up libavdevice61:amd64 (7:7.1.5-0+deb13u1) ...
`#9` 29.64 Setting up ffmpeg (7:7.1.5-0+deb13u1) ...
`#9` 29.65 Processing triggers for libc-bin (2.41-12+deb13u3) ...
`#9` 29.79 �[38;5;79m - Installing pre-requisites�[0m
`#9` 29.79
`#9` 29.79 WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
`#9` 29.79
`#9` 29.84 Hit:1 http://deb.debian.org/debian trixie InRelease
`#9` 29.84 Hit:2 http://deb.debian.org/debian trixie-updates InRelease
`#9` 29.84 Hit:3 http://deb.debian.org/debian-security trixie-security InRelease
`#9` 29.87 Reading package lists...
`#9` 30.44 Building dependency tree...
`#9` 30.59 Reading state information...
`#9` 30.61 All packages are up to date.
`#9` 30.61
`#9` 30.61 WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
`#9` 30.61
`#9` 30.62 Reading pac...
GitHub Actions: Security Scan / trivy: fix(security): sanitize HTTP 500 responses to prevent info disclosure
Conclusion: failure
##[group]Run entrypoint.sh
�[36;1mentrypoint.sh�[0m
shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
env:
CODEQL_ACTION_FEATURE_MULTI_LANGUAGE: false
CODEQL_ACTION_FEATURE_SANDWICH: false
CODEQL_ACTION_FEATURE_SARIF_COMBINE: true
CODEQL_ACTION_FEATURE_WILL_UPLOAD: true
CODEQL_ACTION_VERSION: 4.37.1
CODEQL_ACTION_ANALYSIS_KEY: .github/workflows/security.yml:trivy
CODEQL_WORKFLOW_STARTED_AT:
CODEQL_ACTION_JOB_STATUS: JOB_STATUS_CONFIGURATION_ERROR
INPUT_SCAN_TYPE: image
INPUT_IMAGE_REF: eventrelay:test
INPUT_SCAN_REF: .
INPUT_TRIVYIGNORES: .trivyignore
INPUT_GITHUB_PAT:
INPUT_LIMIT_SEVERITIES_FOR_SARIF:
TRIVY_CACHE_DIR: /home/runner/work/EventRelay/EventRelay/.cache/trivy
##[endgroup]
Found ignorefile '.trivyignore':
# Trivy Ignore File
# This file contains vulnerabilities that are accepted risks or false positives
# Format: CVE-ID or vulnerability ID, one per line
# Comments start with #
# Go crypto certificate validation issues in base images
# These are typically fixed by updating the base image in future releases
# and are not directly actionable in application code
CVE-2025-58183
CVE-2025-61729
# Add other CVEs here as needed with justification comments
Running Trivy with options: trivy image eventrelay:test
INFO [vuln] Vulnerability scanning is enabled
INFO [secret] Secret scanning is enabled
INFO [secret] If your scanning is slow, please try '--scanners vuln' to disable secret scanning
INFO [secret] Please see https://trivy.dev/docs/v0.70/guide/scanner/secret#recommendation for faster secret detection
📣 �[34mNotices:�[0m
- Version 0.72.0 of Trivy is now available, current version is 0.70.0
To suppress version checks, run Trivy scans with the --skip-version-check flag
FATAL Fatal error run error: image scan error: scan error: unable to initialize a scan service: unable to initialize artifact: unable to initialize container image: unable to find the specified image "eventrel...
GitHub Actions: Security Scan / trivy: fix(security): sanitize HTTP 500 responses to prevent info disclosure
Conclusion: failure
##[group]Run github/codeql-action/upload-sarif@v4
with:
sarif_file: trivy-results.sarif
checkout_path: /home/runner/work/EventRelay/EventRelay
***REDACTED***
matrix: null
wait-for-processing: true
##[endgroup]
##[error]Path does not exist: trivy-results.sarif
GitHub Actions: Security Scan / 0_trivy.txt: fix(security): sanitize HTTP 500 responses to prevent info disclosure
Conclusion: failure
`#9` 29.57 Setting up libgbm1:amd64 (25.0.7-2+deb13u1) ...
`#9` 29.57 Setting up libgl1-mesa-dri:amd64 (25.0.7-2+deb13u1) ...
`#9` 29.58 Setting up gcc-14 (14.2.0-19) ...
`#9` 29.58 Setting up librsvg2-2:amd64 (2.60.0+dfsg-1) ...
`#9` 29.59 Setting up libpocketsphinx3:amd64 (0.8+5prealpha+1-15+b4) ...
`#9` 29.59 Setting up libavcodec61:amd64 (7:7.1.5-0+deb13u1) ...
`#9` 29.59 Setting up g++-14-x86-64-linux-gnu (14.2.0-19) ...
`#9` 29.59 Setting up g++-x86-64-linux-gnu (4:14.2.0-1) ...
`#9` 29.60 Setting up curl (8.14.1-2+deb13u4) ...
`#9` 29.60 Setting up g++-14 (14.2.0-19) ...
`#9` 29.60 Setting up libsdl2-2.0-0:amd64 (2.32.4+dfsg-1) ...
`#9` 29.60 Setting up libglx-mesa0:amd64 (25.0.7-2+deb13u1) ...
`#9` 29.61 Setting up libglx0:amd64 (1.7.0-1+b2) ...
`#9` 29.61 Setting up libavformat61:amd64 (7:7.1.5-0+deb13u1) ...
`#9` 29.61 Setting up gcc (4:14.2.0-1) ...
`#9` 29.62 Setting up libgl1:amd64 (1.7.0-1+b2) ...
`#9` 29.63 Setting up libavfilter10:amd64 (7:7.1.5-0+deb13u1) ...
`#9` 29.63 Setting up g++ (4:14.2.0-1) ...
`#9` 29.64 update-alternatives: using /usr/bin/g++ to provide /usr/bin/c++ (c++) in auto mode
`#9` 29.64 Setting up build-essential (12.12) ...
`#9` 29.64 Setting up libavdevice61:amd64 (7:7.1.5-0+deb13u1) ...
`#9` 29.64 Setting up ffmpeg (7:7.1.5-0+deb13u1) ...
`#9` 29.65 Processing triggers for libc-bin (2.41-12+deb13u3) ...
`#9` 29.79 �[38;5;79m - Installing pre-requisites�[0m
`#9` 29.79
`#9` 29.79 WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
`#9` 29.79
`#9` 29.84 Hit:1 http://deb.debian.org/debian trixie InRelease
`#9` 29.84 Hit:2 http://deb.debian.org/debian trixie-updates InRelease
`#9` 29.84 Hit:3 http://deb.debian.org/debian-security trixie-security InRelease
`#9` 29.87 Reading package lists...
`#9` 30.44 Building dependency tree...
`#9` 30.59 Reading state information...
`#9` 30.61 All packages are up to date.
`#9` 30.61
`#9` 30.61 WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
`#9` 30.61
`#9` 30.62 Reading pac...
GitHub Actions: CI / lint-python: fix(security): sanitize all HTTP 500 responses to prevent info disclosure (supersedes #801)
Conclusion: failure
_video_routes.py:8:1
|
7 | import logging
8 | from typing import Dict, List, Optional, Tuple
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9 |
10 | from fastapi import APIRouter, HTTPException
|
UP006 Use `list` instead of `List` for type annotation
--> src/youtube_extension/backend/api/advanced_video_routes.py:33:27
|
31 | """Request for extracting timestamped events."""
32 | video_url: str
33 | event_types: Optional[List[str]] = Field(
| ^^^^
34 | None,
35 | description="Event types to focus on (e.g., ['code_change', 'api_call'])"
|
help: Replace with `list`
UP006 Use `list` instead of `List` for type annotation
--> src/youtube_extension/backend/api/advanced_video_routes.py:65:15
|
63 | """Request for comparing multiple segments."""
64 | video_url: str
65 | segments: List[Tuple[str, str]] = Field(
| ^^^^
66 | ...,
67 | description="List of (start_time, end_time) tuples to compare"
|
help: Replace with `list`
UP006 Use `tuple` instead of `Tuple` for type annotation
--> src/youtube_extension/backend/api/advanced_video_routes.py:65:20
|
63 | """Request for comparing multiple segments."""
64 | video_url: str
65 | segments: List[Tuple[str, str]] = Field(
| ^^^^^
66 | ...,
67 | description="List of (start_time, end_time) tuples to compare"
|
help: Replace with `tuple`
UP006 Use `dict` instead of `Dict` for type annotation
--> src/youtube_extension/backend/api/advanced_video_routes.py:84:13
|
82 | video_url: str
83 | prompt: str
84 | schema: Dict = Field(
| ^^^^
85 | ...,
86 | description="JSON schema for structured output",
|
help: Replace with `dict`
W293 Blank line contains whitespace
--> src/youtube_extension/backend/api/advanced_video_routes.py:111:1
|
109 | ""...
GitHub Actions: CI / 3_guards.txt: fix(security): sanitize all HTTP 500 responses to prevent info disclosure (supersedes #801)
Conclusion: failure
##[group]Run # Opening/closing conflict sentinels always carry a label after the
�[36;1m# Opening/closing conflict sentinels always carry a label after the�[0m
�[36;1m# space, so this never matches decorative "=======" underlines.�[0m
�[36;1mif git grep -nE '^(<<<<<<<|>>>>>>>) ' -- . ':(exclude).github/workflows/ci.yml'; then�[0m
�[36;1m echo "::error::Committed merge-conflict markers found (see matches above)."�[0m
GitHub Actions: CI / 4_lint-python.txt: fix(security): sanitize all HTTP 500 responses to prevent info disclosure (supersedes #801)
Conclusion: failure
_video_routes.py:8:1
|
7 | import logging
8 | from typing import Dict, List, Optional, Tuple
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9 |
10 | from fastapi import APIRouter, HTTPException
|
UP006 Use `list` instead of `List` for type annotation
--> src/youtube_extension/backend/api/advanced_video_routes.py:33:27
|
31 | """Request for extracting timestamped events."""
32 | video_url: str
33 | event_types: Optional[List[str]] = Field(
| ^^^^
34 | None,
35 | description="Event types to focus on (e.g., ['code_change', 'api_call'])"
|
help: Replace with `list`
UP006 Use `list` instead of `List` for type annotation
--> src/youtube_extension/backend/api/advanced_video_routes.py:65:15
|
63 | """Request for comparing multiple segments."""
64 | video_url: str
65 | segments: List[Tuple[str, str]] = Field(
| ^^^^
66 | ...,
67 | description="List of (start_time, end_time) tuples to compare"
|
help: Replace with `list`
UP006 Use `tuple` instead of `Tuple` for type annotation
--> src/youtube_extension/backend/api/advanced_video_routes.py:65:20
|
63 | """Request for comparing multiple segments."""
64 | video_url: str
65 | segments: List[Tuple[str, str]] = Field(
| ^^^^^
66 | ...,
67 | description="List of (start_time, end_time) tuples to compare"
|
help: Replace with `tuple`
UP006 Use `dict` instead of `Dict` for type annotation
--> src/youtube_extension/backend/api/advanced_video_routes.py:84:13
|
82 | video_url: str
83 | prompt: str
84 | schema: Dict = Field(
| ^^^^
85 | ...,
86 | description="JSON schema for structured output",
|
help: Replace with `dict`
W293 Blank line contains whitespace
--> src/youtube_extension/backend/api/advanced_video_routes.py:111:1
|
109 | ""...
GitHub Actions: CI / guards: fix(security): sanitize all HTTP 500 responses to prevent info disclosure (supersedes #801)
Conclusion: failure
##[group]Run # Opening/closing conflict sentinels always carry a label after the
�[36;1m# Opening/closing conflict sentinels always carry a label after the�[0m
�[36;1m# space, so this never matches decorative "=======" underlines.�[0m
�[36;1mif git grep -nE '^(<<<<<<<|>>>>>>>) ' -- . ':(exclude).github/workflows/ci.yml'; then�[0m
�[36;1m echo "::error::Committed merge-conflict markers found (see matches above)."�[0m
GitHub Actions: CI / test: fix(security): sanitize all HTTP 500 responses to prevent info disclosure (supersedes #801)
Conclusion: failure
est_video_processing_service.py::TestProcessVideoToSoftware::test_failed_video_analysis_raises
-------------------------------- live log call ---------------------------------
[ INFO] youtube_extension.backend.services.video_processing_service: Processing video to software: https://www.youtube.com/watch?v=auJzb1D-fag
[ INFO] youtube_extension.backend.services.video_processing_service: ✅ Video processor initialized successfully
[ ERROR] youtube_extension.backend.services.video_processing_service: Video-to-software processing failed: Video processing failed: Analysis failed
PASSED [ 95%]
tests/unit/test_video_processing_service.py::TestProcessVideoToSoftware::test_fallback_to_vercel_when_primary_url_missing
-------------------------------- live log call ---------------------------------
[ INFO] youtube_extension.backend.services.video_processing_service: Processing video to software: https://www.youtube.com/watch?v=auJzb1D-fag
[ INFO] youtube_extension.backend.services.video_processing_service: ✅ Video processor initialized successfully
PASSED [ 95%]
tests/unit/test_video_processing_service.py::TestProcessVideoToSoftware::test_build_failed_when_no_urls
-------------------------------- live log call ---------------------------------
[ INFO] youtube_extension.backend.services.video_processing_service: Processing video to software: https://www.youtube.com/watch?v=auJzb1D-fag
[ INFO] youtube_extension.backend.services.video_processing_service: ✅ Video processor initialized successfully
PASSED [ 95%]
tests/unit/test_video_processing_service.py::TestProcessVideoToSoftware::test_exception_propagated
-------------------------------- live log call ---------------------------------
[ INFO] youtube_extension.backend.services.video_pr...
GitHub Actions: CI / 2_test.txt: fix(security): sanitize all HTTP 500 responses to prevent info disclosure (supersedes #801)
Conclusion: failure
est_video_processing_service.py::TestProcessVideoToSoftware::test_failed_video_analysis_raises
-------------------------------- live log call ---------------------------------
[ INFO] youtube_extension.backend.services.video_processing_service: Processing video to software: https://www.youtube.com/watch?v=auJzb1D-fag
[ INFO] youtube_extension.backend.services.video_processing_service: ✅ Video processor initialized successfully
[ ERROR] youtube_extension.backend.services.video_processing_service: Video-to-software processing failed: Video processing failed: Analysis failed
PASSED [ 95%]
tests/unit/test_video_processing_service.py::TestProcessVideoToSoftware::test_fallback_to_vercel_when_primary_url_missing
-------------------------------- live log call ---------------------------------
[ INFO] youtube_extension.backend.services.video_processing_service: Processing video to software: https://www.youtube.com/watch?v=auJzb1D-fag
[ INFO] youtube_extension.backend.services.video_processing_service: ✅ Video processor initialized successfully
PASSED [ 95%]
tests/unit/test_video_processing_service.py::TestProcessVideoToSoftware::test_build_failed_when_no_urls
-------------------------------- live log call ---------------------------------
[ INFO] youtube_extension.backend.services.video_processing_service: Processing video to software: https://www.youtube.com/watch?v=auJzb1D-fag
[ INFO] youtube_extension.backend.services.video_processing_service: ✅ Video processor initialized successfully
PASSED [ 95%]
tests/unit/test_video_processing_service.py::TestProcessVideoToSoftware::test_exception_propagated
-------------------------------- live log call ---------------------------------
[ INFO] youtube_extension.backend.services.video_pr...
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{py,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{py,js,jsx,ts,tsx}: Use Python 3.9+ and Node 18+ for development
Never hardcode API keys, database URLs, or secrets in code
Make minimal, surgical changes and avoid deleting working code unless fixing security issues
Files:
src/youtube_extension/backend/cloud_ai_routes.pysrc/youtube_extension/backend/real_api_endpoints.pysrc/youtube_extension/backend/cloud_api_endpoints.py
**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.py: Always use type hints for Python functions
Use Black formatter with 88 character line length for Python code
Follow PEP 8 conventions for Python code
Use descriptive variable names and add docstrings to all public functions in Python
Group imports in Python: standard library, third-party, local
Use SQLAlchemy ORM and never write raw SQL queries
Use environment variables via os.getenv() or pydantic-settings to access configuration
Wrap database operations in try-except blocks and use context managers or FastAPI dependencies for connection cleanup
Implement comprehensive error handling with proper logging in all functions
Version APIs using /api/v1/ prefix for stability
Use JSON-RPC 2.0 protocol for all MCP communication
Follow the single-flow workflow: YouTube link → context extraction → agent dispatch → outputs
Use context managers for resource management in Python code
Implement comprehensive input validation and sanitize outputs for security
Use parameterized queries and SQLAlchemy ORM to prevent SQL injection
Define API request/response models using Pydantic for FastAPI endpoints
Store the single unified workflow as the only workflow; never introduce alternate flows or manual triggers
Use SQLAlchemy with connection pooling for database connections and manage sessions with context managers
Provide sensible defaults for non-sensitive configuration in Python settings
Maintain backward compatibility and do not break existing API endpoints
Include comprehensive logging for debugging in MCP implementations
**/*.py: Format Python code with Black using an 88-character line length.
Use Ruff with rules E, W, F, I, B, C4, and UP; E501 is ignored.
Use strict mypy checking with untyped function definitions disallowed.
Files:
src/youtube_extension/backend/cloud_ai_routes.pysrc/youtube_extension/backend/real_api_endpoints.pysrc/youtube_extension/backend/cloud_api_endpoints.py
⚙️ CodeRabbit configuration file
Python backend code. Check for type hints, proper exception handling, async context manager usage, and potential blocking calls in async functions. Flag any bare except clauses or missing timeout parameters on network calls. CRITICAL: Flag any file that contains placeholder/stub implementations — especially in unified_ai_sdk. Any class or function that says "TODO: Replace with production implementation" or returns mock/fake data must be flagged as a blocking issue. Flag any code generation output that reaches users without AST validation or syntax checking.
Files:
src/youtube_extension/backend/cloud_ai_routes.pysrc/youtube_extension/backend/real_api_endpoints.pysrc/youtube_extension/backend/cloud_api_endpoints.py
**/*.{py,js,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Maintain >80% code coverage for new features
Files:
src/youtube_extension/backend/cloud_ai_routes.pysrc/youtube_extension/backend/real_api_endpoints.pysrc/youtube_extension/backend/cloud_api_endpoints.py
**/*.{py,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{py,ts,tsx}: Keep frontend and backend data models synchronized using matching Pydantic (backend) and TypeScript (frontend) interfaces
Use type-safe interfaces for backend-frontend data exchange
Files:
src/youtube_extension/backend/cloud_ai_routes.pysrc/youtube_extension/backend/real_api_endpoints.pysrc/youtube_extension/backend/cloud_api_endpoints.py
**/*
📄 CodeRabbit inference engine (Custom checks)
**/*: Strictly verify that GitHub Copilot has explicitly reviewed and approved the pull request; human approvals alone must not satisfy this check.
Before allowing a merge, require thecopilot-rabbitlabel and AI-generated unit tests committed alongside the code changes; fail the check if either is missing.
**/*: Follow the documented event naming convention<domain>.<entity>.<action>, such asyoutube.video.captured.
Use the service-container dependency injection pattern for backend dependencies.
Never infer SDK types from tests or API documentation alone; use backend response models as the authority.
When auditing branches, use thebranch-cleanupskill and its six-gate fail-test harness; archive branches withgit tag archive/<branch>before deletion, and do not rely on three-dot diffs orgit merge-treefor orphaned branches.For Vercel-specific work, include
https://vercel.com/docs/llms-full.txtin the AI assistant context set.
Files:
src/youtube_extension/backend/cloud_ai_routes.pysrc/youtube_extension/backend/real_api_endpoints.pysrc/youtube_extension/backend/cloud_api_endpoints.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Format Python code with Black using an 88-character line length.
Sort Python imports with isort using the Black profile.
Use Ruff with E, W, F, I, B, C4, and UP rules; E501 is ignored.
Use mypy strict mode; untyped function definitions are disallowed.
Target Python 3.9 or newer.
Validate backend inputs with Pydantic and sanitize subprocess arguments.
Maintain strict mypy type safety in the Python backend.
Use the required Anthropic SDK parametersthinking={"type": "adaptive"}andoutput_config={"effort": "..."}with the current model stringclaude-opus-4-8; do not addTypeErrorcompatibility fallbacks.
src/**/*.py: Do not introduce alternative workflows or manual triggers that bypass the single YouTube link → transcript → events → agents → outputs pipeline.
Use event names in the<domain>.<entity>.<action>format.
Use the service-container dependency-injection pattern for dependencies.
Use Pydantic input validation and sanitize subprocess arguments.
Production code must use real behavior only; do not add mock delays or fake data.
Files:
src/youtube_extension/backend/cloud_ai_routes.pysrc/youtube_extension/backend/real_api_endpoints.pysrc/youtube_extension/backend/cloud_api_endpoints.py
**/*.{py,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{py,ts,tsx,js,jsx}: Do not use mock delays, fake data, or simulated responses in production code; production must remain REAL_MODE_ONLY.
Do not hard-code secrets, keys, or credentials; store them in.envfiles that are gitignored.Do not include secrets or API keys in source code; load them from environment variables instead.
Files:
src/youtube_extension/backend/cloud_ai_routes.pysrc/youtube_extension/backend/real_api_endpoints.pysrc/youtube_extension/backend/cloud_api_endpoints.py
**/*.{py,pyw}
📄 CodeRabbit inference engine (AGENTS.md)
Write Python code to remain compatible with Linux and Windows where possible, including correct handling of
asyncioevent loops.
Files:
src/youtube_extension/backend/cloud_ai_routes.pysrc/youtube_extension/backend/real_api_endpoints.pysrc/youtube_extension/backend/cloud_api_endpoints.py
🪛 GitHub Actions: CI / 4_lint-python.txt
src/youtube_extension/backend/cloud_api_endpoints.py
[error] 12-31: I001 (isort): Import block is un-sorted or un-formatted.
[error] 17-17: UP035 (pyupgrade): typing.Dict is deprecated, use dict instead (imports typing.Dict in from typing ...).
[error] 17-17: UP035 (pyupgrade): typing.List is deprecated, use list instead (imports typing.List in from typing ...).
[error] 53-55: UP006 (pyupgrade): Use dict instead of Dict for type annotation. metadata: Optional[Dict[str, Any]].
[error] 54-56: UP006 (pyupgrade): Use dict instead of Dict for type annotation. transcript: Optional[Dict[str, Any]].
[error] 55-57: UP006 (pyupgrade): Use dict instead of Dict for type annotation. ai_analysis: Optional[Dict[str, Any]].
[error] 67-67: UP006 (pyupgrade): Use dict instead of Dict for type annotation. metadata: Optional[Dict[str, Any]].
[error] 71-71: UP006 (pyupgrade): Use list instead of List for type annotation. video_urls: List[str].
[error] 372-372: F841: Local variable firestore_service is assigned to but never used.
[error] 402-402: F841: Local variable vertex_service is assigned to but never used.
🪛 GitHub Actions: CI / lint-python
src/youtube_extension/backend/cloud_api_endpoints.py
[error] 12-31: I001 Import block is un-sorted or un-formatted. Organize imports.
[error] 17-17: UP035 typing.Dict is deprecated, use dict instead.
[error] 17-17: UP035 typing.List is deprecated, use list instead.
[error] 53-56: UP006 Use dict instead of Dict for type annotation (Optional Dict fields: metadata, transcript, ai_analysis).
[error] 71-72: UP006 Use list instead of List for type annotation. Replace video_urls: List[str] with video_urls: list[str].
[error] 372-372: F841 Local variable firestore_service is assigned to but never used.
[error] 402-402: F841 Local variable vertex_service is assigned to but never used.
🪛 GitHub Actions: CI / test
src/youtube_extension/backend/real_api_endpoints.py
[error] 122-122: Real API processing failed during POST /api/v2/process-video: RuntimeError('crash'). Request returned HTTP 500.
🔇 Additional comments (2)
src/youtube_extension/backend/cloud_api_endpoints.py (1)
254-260: LGTM!Also applies to: 288-294, 326-332
src/youtube_extension/backend/real_api_endpoints.py (1)
145-149: LGTM!Also applies to: 249-253, 382-386
…e leak Addresses CodeRabbit's changes-requested review on the 500-sanitization: - Add exc_info=True to every sanitized 500 handler in cloud_ai_routes, cloud_api_endpoints and real_api_endpoints so the full traceback is actually preserved server-side (the PR claimed this but several handlers logged only str(e)). - real_api_endpoints: re-raise HTTPException before the broad except in batch_process_videos and search_youtube_videos, so the deliberate 400 validation errors (>20 videos / >50 results) are no longer swallowed and rewrapped as 500. Tighten the batch test to assert a clean 400. - cloud_api_endpoints task handler: stop persisting str(e) as the task's error_message. get_video_status / get_video_result echo error_message to clients, so a raw exception there re-exposed internal detail (CWE-209) even though the immediate 500 body was already generic. Persist a static 'Internal server error' and log the real exception with exc_info=True. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JNphZoHcSj1jnc3ofnw5oc
There was a problem hiding this comment.
Pull request overview
This PR hardens FastAPI error handling by sanitizing HTTP 500 responses so clients no longer receive exception messages, stack-adjacent context, or request-derived values via HTTPException(..., detail=...), while relying on server-side logging for diagnostics.
Changes:
- Replaced dynamic 500
detailpayloads (including dict-shaped details) with the static string"Internal server error"across cloud + real API endpoints. - Added/updated unit tests to assert the sanitized 500 response body and introduced a source-scan regression guard test.
- Updated one existing cloud-route test assertion to match the new sanitized behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/youtube_extension/backend/cloud_ai_routes.py | Sanitizes multiple 500 HTTPException.detail strings in cloud AI routes. |
| src/youtube_extension/backend/cloud_api_endpoints.py | Sanitizes 500 details for cloud processing endpoints (including task handler). |
| src/youtube_extension/backend/real_api_endpoints.py | Sanitizes 500 details for real API endpoints. |
| tests/unit/test_500_info_disclosure.py | Adds a regression-guard source scanner intended to prevent reintroducing dynamic 500 details. |
| tests/unit/test_cloud_routes.py | Updates assertion to expect "Internal server error" on a 500 path. |
| tests/unit/test_real_api_endpoints.py | Updates 500-path test to assert no internal state/exception text is leaked. |
|
Addressed the review in
Verified locally: Not addressed (out of scope): the @coderabbitai review Generated by Claude Code |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== PR metadata and changed files =='
gh pr view 814 --json headRefOid,baseRefOid,files --jq '{headRefOid, baseRefOid, files: [.files[].path]}'
echo
echo '== Diff against PR base =='
gh pr diff 814 -- src/youtube_extension/backend/cloud_ai_routes.py \
src/youtube_extension/backend/cloud_api_endpoints.py \
src/youtube_extension/backend/real_api_endpoints.py \
tests/unit/test_real_api_endpoints.py \
tests/unit/test_500_info_disclosure.py || true
echo
echo '== Module outlines =='
ast-grep outline src/youtube_extension/backend/cloud_ai_routes.py --items all --type function || true
ast-grep outline src/youtube_extension/backend/cloud_api_endpoints.py --items all --type function || true
ast-grep outline src/youtube_extension/backend/real_api_endpoints.py --items all --type function || true
echo
echo '== 500 responses, broad handlers, HTTPException propagation, and task error state =='
rg -n -C 5 -e 'status_code\s*=\s*500' \
-e 'except HTTPException' \
-e 'except Exception' \
-e 'error_msg' \
-e '\["error"\]|\["message"\]|error_message' \
src/youtube_extension/backend/cloud_ai_routes.py \
src/youtube_extension/backend/cloud_api_endpoints.py \
src/youtube_extension/backend/real_api_endpoints.py
echo
echo '== Targeted test assertions =='
fd -a 'test_(500_info_disclosure|real_api_endpoints|cloud_api_endpoints)\.py$' tests -x sh -c \
'echo "== $1 =="; rg -n -C 4 -e "more_than_20|does_not_leak|Internal server error|video_url|HTTPException" "$1"' sh {}Length of output: 56856 🏁 Script executed: #!/bin/bash
set -euo pipefail
base="$(gh pr view 814 --json baseRefOid --jq .baseRefOid)"
head="$(gh pr view 814 --json headRefOid --jq .headRefOid)"
echo '== Complete PR diff =='
git diff --unified=50 "$base...$head" -- \
src/youtube_extension/backend/cloud_ai_routes.py \
src/youtube_extension/backend/cloud_api_endpoints.py \
src/youtube_extension/backend/real_api_endpoints.py \
tests/unit/test_500_info_disclosure.py \
tests/unit/test_real_api_endpoints.py \
tests/unit/test_cloud_routes.py
echo
echo '== Exact route and test contexts =='
sed -n '150,185p' src/youtube_extension/backend/real_api_endpoints.py
sed -n '390,443p' src/youtube_extension/backend/real_api_endpoints.py
sed -n '790,815p' tests/unit/test_real_api_endpoints.py
sed -n '1,115p' tests/unit/test_500_info_disclosure.py
echo
echo '== Every 500 HTTPException call form (keyword and positional) =='
rg -n -U -C 2 --glob '*.py' \
'HTTPException\(\s*(?:status_code\s*=\s*500|500\s*,)|status_code\s*=\s*500' \
src/youtube_extension/backend tests
echo
echo '== Dynamic error text in client response/state construction =='
rg -n -C 3 --glob '*.py' \
'(detail|error|error_message)\s*[=:]\s*(?:str\(|f["'\''].*\{.*(?:e|exc|error).*\}|[a-zA-Z_][a-zA-Z0-9_]*error[a-zA-Z0-9_]*)' \
src/youtube_extension/backendLength of output: 1775 🏁 Script executed: #!/bin/bash
set -u -o pipefail
echo '== PR file patches =='
gh api --paginate 'repos/groupthinking/EventRelay/pulls/814/files?per_page=100' \
--jq '.[] | select(.filename == "src/youtube_extension/backend/cloud_ai_routes.py" or .filename == "src/youtube_extension/backend/cloud_api_endpoints.py" or .filename == "src/youtube_extension/backend/real_api_endpoints.py" or .filename == "tests/unit/test_500_info_disclosure.py" or .filename == "tests/unit/test_real_api_endpoints.py" or .filename == "tests/unit/test_cloud_routes.py") | "\n===== \(.filename) =====\n\(.patch // "[patch unavailable]")"'
echo
echo '== Exact route and test contexts =='
sed -n '150,185p' src/youtube_extension/backend/real_api_endpoints.py
sed -n '390,443p' src/youtube_extension/backend/real_api_endpoints.py
sed -n '790,815p' tests/unit/test_real_api_endpoints.py
sed -n '1,115p' tests/unit/test_500_info_disclosure.py
echo
echo '== Every 500 HTTPException call form (keyword and positional) =='
rg -n -U -C 2 --glob '*.py' \
'HTTPException\(\s*(?:status_code\s*=\s*500|500\s*,)|status_code\s*=\s*500' \
src/youtube_extension/backend tests || true
echo
echo '== Dynamic error text in client response/state construction =='
rg -n -C 3 --glob '*.py' \
'(detail|error|error_message)\s*[=:]\s*(?:str\(|f["'\''].*\{.*(?:e|exc|error).*\}|[a-zA-Z_][a-zA-Z0-9_]*error[a-zA-Z0-9_]*)' \
src/youtube_extension/backend || trueLength of output: 74637
Two regression-test gaps remain:
✅ Action performedReview finished.
|
Closes the two regression-test gaps CodeRabbit/Copilot flagged: - Rewrite test_500_info_disclosure guard from a keyword-only regex to an AST scan. It now flags every 500 HTTPException whose detail is not a static string literal — positional HTTPException(500, str(e)), keyword detail=str(e)/f-strings, dicts, and bare variables (detail=error_msg) — and is explicitly scoped to the routers this PR hardens (cloud_ai_routes, cloud_api_endpoints, real_api_endpoints), with a docstring that no longer over-claims tree-wide coverage. Legacy leaks elsewhere (e.g. api/advanced_video_routes.py) are out of scope and tracked separately. Synthetic self-checks cover each supported form plus static/4xx negatives. - Tighten test_max_results_above_50 to assert a clean 400 + limit message (was accepting 400 or 500), matching the batch-limit test now that the search handler re-raises HTTPException before its broad except. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JNphZoHcSj1jnc3ofnw5oc
…onse boundary Flagged by Copilot and Vercel's agent (VADE, security): the video processor's normal failure path persists raw exception text (str(e)) into processing state, which /status, /result, and the sync process-video response echoed back to clients via error_message/error (CWE-209) — the endpoint-level 500 handler only covered the raise path, not this persisted path. Add _client_safe_error() and apply it at all three client boundaries in cloud_api_endpoints.py: clients now get a generic 'Internal server error' when a failure occurred, while the full message stays in server-side state and logs. Fixing at the boundary covers every persistence path without changing the processor module or its internal-facing result.error_message (which its own tests assert). Update test_process_video_sync_failed to assert the sanitized body and that the raw message never appears in the response. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JNphZoHcSj1jnc3ofnw5oc
|
Update: follow-up (2) is now fixed too, not deferred — Vercel's agent also flagged it as a security leak, so with two reviewers concurring I brought it in scope via the response-boundary approach Copilot suggested (keeps the fix inside In With this, all review findings from CodeRabbit, Copilot, and Vercel's agent are addressed. No deferred items remain. Generated by Claude Code |
Copilot: the guard scans 500s only and the endpoint tests asserted status codes alone, so a regression re-leaking str(e) in the newly sanitized 429 (RateLimitError) or 503 (CloudAIError) branch would pass unnoticed. Assert the exact static bodies and the absence of the exception text in test_analyze_video_rate_limit_error (429 -> 'Rate limit exceeded') and test_analyze_video_cloud_ai_error (503 -> 'AI service temporarily unavailable'). Also replace the guard's misleading dynamic-429 'safe' control with a genuine client-input 4xx example and note where 429/503 coverage lives. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JNphZoHcSj1jnc3ofnw5oc
…well-dczolv # Conflicts: # tests/unit/test_cloud_routes.py
groupthinking
left a comment
There was a problem hiding this comment.
I reviewed the latest head and don’t see a remaining correctness or security issue in the changed paths.
What changed here is solid:
- 500 responses in the touched routers are now sanitized instead of echoing exception text.
- the
analyze_videoexception ordering is fixed soRateLimitErrorandConfigurationErrorno longer fall through the baseCloudAIErrorhandler. - persisted failure state is sanitized at the response boundary, which closes the polling/status leak.
- regression coverage was tightened so the 400/429/500/503 paths now assert sanitized bodies instead of only status codes.
One remaining caveat: GitHub still reports this PR as not cleanly mergeable right now, so I’d resolve that before landing.
Net: no blocking code issue from me on the current diff; the remaining work looks like branch/merge hygiene rather than logic changes.
The merge-base changed after approval.
|
Since the review,
Net: merging #814 would conflict against restructured code and downgrade Recommendation: disable auto-merge and close #814 as superseded (I've left it unmerged and unresolved so auto-merge stays safely stalled on the conflict — nothing will land by accident). One residual worth a fresh follow-up (not this PR): Happy to open that follow-up or close this out on your say-so. Generated by Claude Code |
The merge-base changed after approval.
|
Closing as superseded by Generated by Claude Code |
Summary
Closes remaining HTTP 500 information-disclosure leaks (CWE-209) across the cloud and real API routers. Exception messages, stack context, and request-derived values (
video_url, timestamps) were being returned to clients inside theHTTPExceptiondetailfield. This replaces every such dynamic detail with a static"Internal server error"string while preserving — and in several cases adding — server-sidelogger.error(..., exc_info=True)so operators keep full diagnostics.Changes
cloud_ai_routes.py— 5 handlers: dynamicdetail=f"...{str(e)}"→ static"Internal server error".cloud_api_endpoints.py— replaced dict-shaped 500 details (leakingmessage/video_url/timestamp) with a static string; addedlogger.error(..., exc_info=True)where missing.real_api_endpoints.py— same treatment across 6 handlers, including the dict-shaped leak in the v2 process endpoint.tests/unit/test_500_info_disclosure.py— new static-guard test that scans the router source for dynamic 500 details and fails on any leak (plus a synthetic-leak positive control).tests/unit/test_cloud_routes.py— updated one assertion to match the sanitized response body.Verification
pytest tests/unit/test_500_info_disclosure.py→ 2 passed (guard + synthetic-leak control).test_cloud_routes.pyassertion updated todetail == "Internal server error".Notes
Supersedes the earlier #801 approach and overlaps thematically with the other open 500-hardening PRs (#804, #807, #810). Left as a draft pending human review and de-duplication against those PRs before merge.
🤖 Generated with Claude Code
Generated by Claude Code